
///
/// This is the implementation of the Handler class
///
//#include "sys/types.h"
#include "stdlib.h"
#include "netinet/in.h"
#include "Handler.h"
#include <cstring>
#include <iostream>
//#include <cstdio>
#include <cassert>
#include <string>
#include "Server.h"
#include "Client.h"
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "common.h"
#include "Cache.h"

Handler::Handler(const Handler& rhs)
{
	mSockFD = rhs.mSockFD;
	mMsgBufPtr = rhs.mMsgBufPtr;
	mType = rhs.mType;
	memcpy(mMsgBuf, rhs.mMsgBuf, MSG_BUF_SIZE);
	
	mLineHeadersReceived = rhs.mLineHeadersReceived;
	mEntityBytesToEat = rhs.mEntityBytesToEat;
	
	mEntityBuf = rhs.mEntityBuf;
	mEntityBufLength = rhs.mEntityBufLength;
	mEntityBufPtr = rhs.mEntityBufPtr;

	mUri = rhs.mUri;

	mOriginFD = rhs.mOriginFD;
}

Handler& Handler::operator=(const Handler& copy)
{
	mSockFD = copy.mSockFD;
	mMsgBufPtr = copy.mMsgBufPtr;
	mType = copy.mType;
	memcpy(mMsgBuf, copy.mMsgBuf, MSG_BUF_SIZE);
	
	mLineHeadersReceived = copy.mLineHeadersReceived;
	mEntityBytesToEat = copy.mEntityBytesToEat;

	mEntityBuf = copy.mEntityBuf;
	mEntityBufLength = copy.mEntityBufLength;
	mEntityBufPtr = copy.mEntityBufPtr;

	mUri = copy.mUri;

	mOriginFD = copy.mOriginFD;

	return *this;
}

int Handler::HandleMessage(const char *packet, const unsigned int packetLength)
{
#if 0
	std::cout << ".";
	for(unsigned int i = 0; i < packetLength; ++i, ++packet)
		printf("%2x", *packet);
	std::cout << "." << std::endl;
#endif

	unsigned int bytesToEat = packetLength;
	const char * packetEatingPoint = packet;
	size_t pos;
	unsigned int bytesNeeded; 
	
	while(bytesToEat != 0)
	{
		if(mLineHeadersReceived == false)
		{
			std::string temp(packetEatingPoint, bytesToEat);
			//TODO: there's a bug here, the double crlf could break into 
			// 2 TCP packets. I assume that this pattern occurs together.
			if((pos = temp.find(DOUBLE_CRLF)) == std::string::npos) // not yet
			{
				bytesNeeded = bytesToEat;
			}
			else
			{
				bytesNeeded = pos + 4; // eat uptil end of line+headers
				mLineHeadersReceived = true;
			}
			
			memcpy(mMsgBuf + mMsgBufPtr, packetEatingPoint, bytesNeeded);
			bytesToEat -= bytesNeeded;
			packetEatingPoint += bytesNeeded;
			mMsgBufPtr += bytesNeeded;

			if(mLineHeadersReceived == true)
			{
				std::cout << "Header reception done!" << std::endl;
				mEntityBytesToEat = mEntityBufLength = mEntityBufPtr = 0;
				ProcessHeaderInBuffer(); //this sets the entity bytes to eat
				if(mEntityBytesToEat == 0)
				{
					// EARLY ACT ON THE MESSAGE HERE	
					ActOnMessage();

					mMsgBufPtr = 0;
					mEntityBufPtr = mEntityBufLength = mEntityBytesToEat = 0;
					mLineHeadersReceived = false;
				}
			}
		}
		else
		{ 
			if(mEntityBytesToEat != 0)
			{
				//pick as much as you need from whats given
				bytesNeeded = std::min(bytesToEat, mEntityBytesToEat);

				memcpy(mEntityBuf + mEntityBufPtr, packetEatingPoint, bytesNeeded);
				bytesToEat -= bytesNeeded;
				packetEatingPoint += bytesNeeded;
				mEntityBufPtr += bytesNeeded;
				mEntityBytesToEat -= bytesNeeded;
			}

			
			if(mEntityBytesToEat == 0) // we got what we wanted
			{
				// ACT ON THE MESSAGE HERE	
				ActOnMessage();

				mMsgBufPtr = 0;
				mEntityBytesToEat = mEntityBufPtr = mEntityBufLength = 0;
				mLineHeadersReceived = false;
			}
		}
	}

	return 0;
}

int Handler::ProcessHeaderInBuffer()
{
	std::string temp(mMsgBuf, mMsgBufPtr);
	size_t pos = temp.find(CONTENT_LENGTH);
	if(pos == std::string::npos)
	{
		mEntityBytesToEat = mEntityBufLength = 0;
	}
	else
	{
		size_t endpos = temp.find(CRLF, pos);
		assert(endpos != std::string::npos);
		std::string lengthStr(temp, pos + strlen(CONTENT_LENGTH), 
								endpos - pos - strlen(CONTENT_LENGTH));
		
		mEntityBytesToEat = mEntityBufLength = atoi(lengthStr.c_str());
	}
	std::cout << "Expecting Content-Length: " << mEntityBytesToEat << std::endl;
	
	assert(mEntityBuf == NULL);
	if(mEntityBufLength != 0)
		mEntityBuf = new char[mEntityBufLength];

	return 0;
}

int Handler::ActOnMessage()
{
	std::string msg(mMsgBuf, mMsgBufPtr);
	msg.append(mEntityBuf, mEntityBufLength);
	
	//the entity buffer's job is done
	if(mEntityBuf != NULL)
	{
		delete[] mEntityBuf;
		mEntityBuf = NULL;
	}
	
	size_t pos = msg.find(HTTP);
	assert(pos != std::string::npos);

	if(pos == 0)
	{
		if(mType == CLIENT)
		{
			// dump msg on stdout and die
			std::cout << "CLIENT RECEIVED THE FOLLOWING PAGE FROM PROXY:"
						<< "\n##########################\n" 
						<< msg.c_str() 
						<< "\n##########################\n";

			Client::GetInstance()->Die();
		}
		else if(mType == PROXY)
		{
			// response from origin server
			// send to client and add to cache
			std::cout << "Origin server response received!" << std::endl;
			Cache::GetInstance()->SetPage(mUri, msg);

			int retVal = send(mSockFD, msg.c_str(), msg.length(), 0);
			
			std::cout << "Origin's response forwarded to client!" << std::endl;

			Server::GetInstance()->Die(mSockFD, mOriginFD);

			return retVal;
		}
		else
			assert(false);
		
	}
	else
	{
		// request from client
		if(msg[0] == 'G') // GET
		{
			assert(mType == PROXY); //TODO: remove

			std::cout << "GET received!" << std::endl;

			mUri = std::string(msg, 4, pos-1-4); // "GET_xxx_HTTP"
			std::cout << "URI: " << mUri.c_str() << std::endl;
			
			//TODO: now perform the cache check and LRU business
			if(Cache::GetInstance()->ExistsAndValid(mUri))
			{
				//send this to the client and close socket
				std::string& page = Cache::GetInstance()->GetPage(mUri);

				return send(mSockFD, page.c_str(), page.length(), 0);
			}
			else
			{
				// assume uri is 'http://xxx/yy/zz'
				size_t endpos = mUri.find_first_of("/", 7); // ignore 'http://'
				std::string hostname;
				if(endpos == std::string::npos) // only hostname given
				{
					hostname = std::string(mUri, 7);
				}
				else
				{
					hostname = std::string(mUri, 7, endpos - 7);
				}
				std::cout << "Origin Server: " << hostname.c_str() << std::endl;

				static int status; 
				static struct addrinfo hints; 
				static struct addrinfo *res;

				memset(&hints, 0, sizeof hints); 
				hints.ai_family = AF_INET;	
				hints.ai_socktype = SOCK_STREAM;

				if((status = getaddrinfo(hostname.c_str(), "80", &hints, &res)) != 0)
				{
					std::cout << "getaddrinfo() error: " 
						<< gai_strerror(status) << std::endl;

					return -1;
				}

				mOriginFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
				if(mOriginFD == -1)
				{
					perror("socket() error: ");
					return -1;
				}

				if(connect(mOriginFD, res->ai_addr, res->ai_addrlen) == -1)
				{
					perror("connect() error: ");
					close(mOriginFD);
				}

				std::cout << "Connected successfully to server!" << std::endl;

				freeaddrinfo(res);

				//now add this new socket to fd_set, and map it to mainFD
				Server::GetInstance()->MapOriginToClientFD(mOriginFD, mSockFD);
	
				//now send http request to origin server
				sprintf(mMsgBuf, "GET %s HTTP/1.0%s", mUri.c_str(), DOUBLE_CRLF);

				std::cout << "Sending HTTP GET message: " << std::endl << mMsgBuf
					<< std::endl;

				return send(mOriginFD, mMsgBuf, strlen(mMsgBuf), 0);
			}
		}
	}
	return 0;
}
